package com.datastructure3.hash;

import org.junit.Test;

import javax.management.remote.rmi._RMIConnection_Stub;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: 临晖
 * @since: 2024-07-20
 */
public class Solution {
    /**
     * 242. 有效的字母异位词
     * 已解答
     * 简单
     * 相关标签
     * 相关企业
     * 给定两个字符串 s 和 t ，编写一个函数来判断 t 是否是 s 的字母异位词。
     * 注意：若 s 和 t 中每个字符出现的次数都相同，则称 s 和 t 互为字母异位词。
     *
     * @param s
     * @param t
     * @return
     */
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }

        int[] arr = new int[26];

        for (int i = 0; i < s.length(); i++) {
            arr[s.charAt(i) - 'a']++;
        }


        for (int i = 0; i < t.length(); i++) {
            int temp;
            temp = --arr[t.charAt(i) - 'a'];

            if (temp < 0) {
                return false;
            }
        }

        return true;
    }


    /**
     * 383. 赎金信
     * 已解答
     * 简单
     * 相关标签
     * 相关企业
     * 给你两个字符串：ransomNote 和 magazine ，判断 ransomNote 能不能由 magazine 里面的字符构成。
     * 如果可以，返回 true ；否则返回 false 。
     * magazine 中的每个字符只能在 ransomNote 中使用一次。
     *
     * @param ransomNote
     * @param magazine
     * @return
     */
    public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length() > magazine.length()) {
            return false;
        }


        int[] arr = new int[26];
        for (int i = 0; i < magazine.length(); i++) {
            arr[magazine.charAt(i) - 'a']++;
        }

        for (int i = 0; i < ransomNote.length(); i++) {
            int temp;
            temp = --arr[ransomNote.charAt(i) - 'a'];

            if (temp < 0) {
                return false;
            }
        }

        return true;
    }


    /**
     * 49. 字母异位词分组
     * 已解答
     * 中等
     * 相关标签
     * 相关企业
     * 给你一个字符串数组，请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
     * 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
     *
     * @param strs
     * @return
     */
    public List<List<String>> groupAnagrams(String[] strs) {


        HashMap<String, List<String>> map = new HashMap<>();
        String str;
        for (int i = 0; i < strs.length; i++) {
            char[] array = strs[i].toCharArray();
            Arrays.sort(array);
            str = new String(array);
            List list = map.getOrDefault(str, new ArrayList());
            //添加
            list.add(strs[i]);
            map.put(str, list);
        }

        return new ArrayList<>(map.values());


    }


    /**
     * 438. 找到字符串中所有字母异位词
     * 已解答
     * 中等
     * 相关标签
     * 相关企业
     * 给定两个字符串 s 和 p，找到 s 中所有 p 的 异位词 的子串，返回这些子串的起始索引。不考虑答案输出的顺序。
     * 异位词 指由相同字母重排列形成的字符串（包括相同的字符串）
     *
     * @param s
     * @param p
     * @return
     */
    public List<Integer> findAnagrams(String s, String p) {

        ArrayList<Integer> res = new ArrayList<>();
        if (p.length() > s.length()) {
            return res;
        }
        //单词p的长度
        int len = p.length();

        int[] arr1 = new int[26];
        int[] arr2 = new int[26];
        for (int i = 0; i < len; i++) {
            arr1[p.charAt(i) - 'a']++;
            arr2[s.charAt(i) - 'a']++;
        }

        //Arrays.equals(arr1, arr2);

        if (isEquals(arr1, arr2)) {
            res.add(0);
        }

        for (int i = len; i < s.length(); i++) {
            arr2[s.charAt(i) - 'a']++;
            arr2[s.charAt(i - len) - 'a']--;
            if (isEquals(arr1, arr2)) {
                res.add(i - len + 1);
            }
        }

        return res;

    }

    //可以用 Arrays.equals(arr1, arr2); 替代；
    public boolean isEquals(int[] arr1, int[] arr2) {
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }

        return true;
    }


    /**
     * 349. 两个数组的交集
     * 已解答
     * 简单
     * 相关标签
     * 相关企业
     * 给定两个数组 nums1 和 nums2 ，返回 它们的
     * 交集
     * 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序
     *
     * @param nums1
     * @param nums2
     * @return
     */
    public int[] intersection(int[] nums1, int[] nums2) {
        ArrayList<Integer> list = new ArrayList<>();

        Arrays.sort(nums1);
        Arrays.sort(nums2);

        int start = 0;
        for (int i = 0; i < nums1.length; i++) {

            if (i > 0 && nums1[i] == nums1[i - 1]) {
                continue;
            }

            for (int j = start; j < nums2.length; j++) {
                if (nums1[i] == nums2[j]) {
                    list.add(nums1[i]);
                    start = j + 1;
                    break;
                }
            }

        }
        int[] res = new int[list.size()];
        for (int i = 0; i < res.length; i++) {
            res[i] = list.get(i);
        }
        return res;
    }


    /**
     * 350. 两个数组的交集 II
     * 已解答
     * 简单
     * 相关标签
     * 相关企业
     * 给你两个整数数组 nums1 和 nums2 ，请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数，应与元素在两个数组中都出现的次数一致（如果出现次数不一致，则考虑取较小值）。可以不考虑输出结果的顺序。
     *
     * @param nums1
     * @param nums2
     * @return
     */
    public int[] intersect(int[] nums1, int[] nums2) {
        ArrayList<Integer> list = new ArrayList<>();

        Arrays.sort(nums1);
        Arrays.sort(nums2);

        int start = 0;
        for (int i = 0; i < nums1.length; i++) {

            //if (i > 0 && nums1[i] == nums1[i - 1]) {
            //    continue;
            //}

            for (int j = start; j < nums2.length; j++) {
                if (nums1[i] == nums2[j]) {
                    list.add(nums1[i]);
                    start = j + 1;
                    break;
                } else if (nums1[i] < nums2[j]) {
                    break;
                }
            }

        }
        int[] res = new int[list.size()];
        for (int i = 0; i < res.length; i++) {
            res[i] = list.get(i);
        }
        return res;
    }


    /**
     * 202. 快乐数
     * 已解答
     * 简单
     * 相关标签
     * 相关企业
     * 编写一个算法来判断一个数 n 是不是快乐数。
     * 「快乐数」 定义为：
     * 对于一个正整数，每一次将该数替换为它每个位置上的数字的平方和。
     * 然后重复这个过程直到这个数变为 1，也可能是 无限循环 但始终变不到 1。
     * 如果这个过程 结果为 1，那么这个数就是快乐数。
     * 如果 n 是 快乐数 就返回 true ；不是，则返回 false
     *
     * @param n
     * @return
     */
    public boolean isHappy(int n) {


        List<Integer> list = new ArrayList<>();
        while (n != 1) {
            n = getNum(n);
            if (list.contains(n)) {
                return false;
            }
            list.add(n);
        }

        return true;
    }

    public int getNum(int num) {
        int sum = 0;
        while (num != 0) {
            int squ = (int) Math.pow(num % 10, 2);
            sum += squ;
            num /= 10;
        }

        return sum;
    }


    /**
     * 1. 两数之和
     * 已解答
     * 简单
     * 相关标签
     * 相关企业
     * 提示
     * 给定一个整数数组 nums 和一个整数目标值 target，请你在该数组中找出 和为目标值 target  的那 两个 整数，并返回它们的数组下标。
     * 你可以假设每种输入只会对应一个答案。但是，数组中同一个元素在答案里不能重复出现。
     * 你可以按任意顺序返回答案。
     *
     * @param nums
     * @param target
     * @return
     */
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(nums[0], 0);

        for (int i = 1; i < nums.length; i++) {
            int sub = target - nums[i];
            if (map.containsKey(sub)) {
                return new int[]{map.get(sub), i};
            } else {
                map.put(nums[i], i);
            }
        }

        return new int[]{-1, -1};

    }


    /**
     * 454. 四数相加 II
     * 已解答
     * 中等
     * 相关标签
     * 相关企业
     * 给你四个整数数组 nums1、nums2、nums3 和 nums4 ，数组长度都是 n ，请你计算有多少个元组 (i, j, k, l) 能满足：
     * 0 <= i, j, k, l < n
     * nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
     *
     * @param nums1
     * @param nums2
     * @param nums3
     * @param nums4
     * @return
     */
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {


        int count = 0;
        //值，数量
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++) {
                Integer value = map.getOrDefault(nums1[i] + nums2[j], 0);
                value++;
                map.put(nums1[i] + nums2[j], value);
            }
        }

        for (int i = 0; i < nums3.length; i++) {
            for (int j = 0; j < nums4.length; j++) {
                int sum = nums3[i] + nums4[j];
                int sub = 0 - sum;

                if (map.containsKey(sub)) {
                    Integer value = map.get(sub);
                    count += value;
                }
            }
        }

        return count;
    }


    /**
     * 15. 三数之和
     * 已解答
     * 中等
     * 相关标签
     * 相关企业
     * 提示
     * 给你一个整数数组 nums ，判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ，同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
     * 注意：答案中不可以包含重复的三元组。
     *
     * @param nums
     * @return
     */
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> list = new ArrayList<>();

        Arrays.sort(nums);

        for (int i = 0; i < nums.length; i++) {

            //剪枝
            if (nums[i] > 0) {
                break;
            }

            //放重复
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            int left = i + 1;
            int right = nums.length - 1;

            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == 0) {
                    list.add(Arrays.asList(nums[i], nums[left], nums[right]));

                    //放重复
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    ;
                    while (right > left && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    ;

                    left++;
                    right--;
                } else if (sum > 0) {
                    right--;
                } else {
                    left++;
                }
            }

        }

        return list;
    }


    /**
     *
     * 18. 四数之和
     * 已解答
     * 中等
     * 相关标签
     * 相关企业
     * 给你一个由 n 个整数组成的数组 nums ，和一个目标值 target 。
     * 请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] （若两个四元组元素一一对应，则认为两个四元组重复）：
     *
     * @param nums
     * @param target
     * @return
     */
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();

        //排序
        Arrays.sort(nums);

        for (int i = 0; i < nums.length; i++) {

            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            if (target < 0 && nums[i] > 0) {
                break;
            }

            if (target > 0 && nums[i] > target) {
                break;
            }

            for (int j = i + 1; j < nums.length; j++) {

                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }


                int left = j + 1;
                int right = nums.length - 1;

                while (left < right) {
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];

                    if (sum == target) {
                        res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));

                        while (left < right && nums[left + 1] == nums[left]) {
                            left++;
                        }

                        while (left < right && nums[right - 1] == nums[right]) {
                            right--;
                        }

                        left++;
                        right--;
                    } else if (sum > target) {
                        right--;
                    } else {
                        left++;
                    }
                }
            }
        }

        return res;
    }


    @Test
    public void testSolution() {
        //String[] strs = {"eat","tea","tan","ate","nat","bat"};
        //groupAnagrams(strs);
        //isHappy(7);
        int[] arr = {-1, 0, 1, 2, -1, -4};
        threeSum(arr);
    }


}
